﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Reactor.Messaging
{
    public static class ExtensionMethods
    {
        public static bool IsMessageHandler(this Type t)
        {
            if (t.IsAbstract || t.IsInterface) return false;

            foreach (Type interfaceType in t.GetInterfaces())
            {
                var messageType = GetDeclaredMessageTypeHandledByInterface(interfaceType);
                if (messageType != null) return true;
            }

            return false;
        }

        public static bool CanAcceptMessageType(this Type messageHandlerType, Type messageType)
        {
            var declaredMessageTypeHandled = GetDeclaredMessageTypeHandledByConcreteType(messageHandlerType);
            if (declaredMessageTypeHandled == null) return false;

            // Check for direct type match
            if (messageType == declaredMessageTypeHandled) return true;

            var interfaces = messageType.GetInterfaces();
            var baseTypes = messageType.GetAllBaseTypes();

            // Check to see if handler declared an interface type for its message
            if(declaredMessageTypeHandled.IsInterface)
            {
                // Handler is declared to handle a particular interface implementation.
                // Check to see if the actual message type implements that interface.
                return interfaces.Any(i => i == declaredMessageTypeHandled);
            }

            // Check to see if handler's declared message type for inheritance matches
            return baseTypes.Any(t => t == declaredMessageTypeHandled);
        }

        public static IEnumerable<Type> GetAllPossibleMessageTypesHandled(this Type messageHandlerType)
        {
            var declaredMessageTypeHandled = messageHandlerType.GetDeclaredMessageTypeHandledByConcreteType();
            if(declaredMessageTypeHandled == null) return new Type[0];

            var types = new List<Type>();
            types.AddRange(declaredMessageTypeHandled.GetInterfaces());
            types.AddRange(declaredMessageTypeHandled.GetAllBaseTypes());
            types.Add(declaredMessageTypeHandled);
            types.RemoveAll(t => t == typeof (Object));

            return types;
        }

        public static Type GetDeclaredMessageTypeHandledByConcreteType(this Type type)
        {
            if (type.IsInterface || type.IsAbstract) throw new InvalidOperationException("Provided System.Type cannot be an interface or abstract type.");

            // Find message handler interface and interrogate it
            var interfaces = type.GetInterfaces();

            foreach (var i in interfaces)
            {
                var declaredMsgType = GetDeclaredMessageTypeHandledByInterface(i);
                if (declaredMsgType != null) return declaredMsgType;
            }

            return null;
        }

        #region Private Helper Methods

        private static Type GetDeclaredMessageTypeHandledByInterface(Type interfaceType)
        {
            if (!interfaceType.IsInterface) throw new InvalidOperationException("Provided System.Type is not an interface type.");

            if (interfaceType.IsGenericType && !interfaceType.IsGenericTypeDefinition)
            {
                var handlerGenericType = interfaceType.GetGenericTypeDefinition();
                if (handlerGenericType == typeof(IMessageHandler<>))
                {
                    var args = interfaceType.GetGenericArguments();
                    return args[0];
                }
            }

            return null;
        }

        #endregion
    }
}
